#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project ：python_learning 
@File ：ConfigManager.py
@IDE  ：PyCharm 
@Author ：李涵彬
@Date ：2024/4/10 下午3:27 
"""
import logging
import os
import json
from typing import Any, Dict, Optional

from enum import Enum

from dotenv import load_dotenv


class Environment(Enum):
	"""定义可能的运行环境"""

	DEVELOPMENT = "development"
	PRODUCTION = "production"
	TESTING = "testing"


class EnvironmentUtil:
	"""环境工具"""

	@staticmethod
	def get_environment() -> str:
		"""
		获取当前运行的环境

		返回:
			str: 当前环境的字符串表示。该值来源于环境变量 ENVIRONMENT，若未设置则默认为 'development'。
		"""
		# 加载 .env 文件中的环境变量，建议在应用启动时调用一次即可
		load_dotenv()

		# 获取环境变量值，并提供默认值
		env_value = os.getenv("ENVIRONMENT")
		if env_value is None:
			print("警告：环境变量 ENVIRONMENT 未设置，将使用默认值 'development'。")
			return Environment.DEVELOPMENT.value

		# 检查环境值是否有效，若无效则记录警告并返回默认值
		if env_value not in [env.value for env in Environment]:
			print("警告：环境变量 ENVIRONMENT 值无效，将使用默认值 'development'。")
			return Environment.DEVELOPMENT.value

		return env_value


class ConfigLoader:
	"""配置加载器"""

	@staticmethod
	def load_config(file_path: str) -> Dict[str, Any]:
		"""
		从 JSON 文件加载配置

		参数:
		file_path: str - 配置文件的绝对路径

		返回值:
		Dict[str, Any] - 加载的配置数据，类型不定
		"""
		# 增加日志记录
		logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
		logging.info(f"开始加载配置文件: {file_path}")

		# 检查文件路径安全性
		if not os.path.isabs(file_path):
			raise ValueError("文件路径必须是绝对路径。")
		if '..' in file_path.split(os.sep):
			raise ValueError("文件路径不能包含 '..'。")

		# 检查文件是否存在并可读
		if not os.path.exists(file_path) or not os.access(file_path, os.R_OK):
			logging.error(f"无法访问文件: {file_path}")
			raise FileNotFoundError(f"配置文件 {file_path} 不存在或不可读。")

		try:
			with open(file_path, "r", encoding='utf-8') as f:
				config = json.load(f)
				logging.info(f"配置加载成功: {file_path}")
				return config
		except json.JSONDecodeError:
			logging.error(f"配置文件格式错误: {file_path}")
			raise ValueError("配置文件格式错误，请检查JSON格式。")
		except FileNotFoundError:
			# 这个异常处理是多余的，因为上面已经通过os.path.exists()进行了检查，这里保留只是为了逻辑上更清晰
			logging.error(f"配置文件不存在: {file_path}")
			raise


class ErrorHandler:
	"""错误处理"""

	@staticmethod
	def handle_exception(exception: Exception, error_type: str) -> None:
		"""
		统一异常处理逻辑。

		参数:
		exception: Exception - 发生的异常对象。
		error_type: str - 异常的类型或错误的描述。

		返回值:
		无
		"""
		# 确保error_type不为空
		if not error_type:
			raise ValueError("error_type cannot be empty")

		# 获取异常类型
		exception_type = type(exception).__name__

		# 记录异常信息，优化为只记录简短信息和异常类型
		message = f"Failed to load config: {error_type}: {exception_type}: {str(exception)}"

		# 使用error级别记录异常信息
		logging.error(message)

		# 根据异常类型决定是否进行重试或其他恢复策略
		if exception_type == "TimeoutError":
			print("Connection timed out, retrying...")
		# 对于其他异常类型，提示可能需要手动干预
		else:
			print("An error occurred, consider manual intervention if necessary.")


class PathChecker(object):
	"""路径检查器"""

	@staticmethod
	def check_path(file_path: str):
		"""
		检查给定的文件路径是否合法、存在，并且判断是否可以读取和执行。

		参数:
		- file_path: str，指定的文件路径。

		抛出:
		- ValueError: 如果路径包含潜在的目录遍历攻击字符（如'..'或以'/'开头），或路径为空。
		- FileNotFoundError: 如果指定的文件不存在。
		- ValueError: 如果文件存在但读取时发现值不合法。
		- Exception: 如果发生预期之外的错误。
		"""
		# 检查路径是否合法，防止目录遍历攻击
		if '..' in file_path or file_path.startswith('/'):
			raise ValueError("Invalid config path: potential directory traversal detected")

		# 验证输入是否为非空字符串
		if not isinstance(file_path, str) or not file_path:
			raise ValueError("path must be a non-empty string")

		# 检查文件是否存在
		if not os.path.isfile(file_path):
			raise FileNotFoundError(f"File not found: {file_path}")

		try:
			default_config = ConfigLoader.load_config(file_path)  # 尝试加载配置文件
			return default_config
		except FileNotFoundError as e:
			ErrorHandler.handle_exception(e, "File not found")  # 处理文件不存在的异常
			raise e
		except ValueError as e:
			ErrorHandler.handle_exception(e, "Invalid value in file")  # 处理配置文件中值不合法的异常
			raise e
		except Exception as e:
			ErrorHandler.handle_exception(e, "Unexpected error")  # 处理其他意外异常
			raise e


class ConfigManager:
	"""配置管理器"""

	def __init__(self):
		"""
		初始化配置管理器
		"""

	@staticmethod
	def get_environment() -> str:
		"""
		获取当前运行的环境

		无参数

		返回值:
		   str: 当前运行环境的字符串表示
		"""
		return EnvironmentUtil.get_environment()  # 获取环境信息

	@staticmethod
	def load_config(file_path: str) -> Dict[str, Any]:
		"""
		从 JSON 文件加载配置

		参数:
		file_path: str - 配置文件的路径

		返回值:
		Dict[str, Any] - 加载的配置，以字典形式返回，键值对为配置的名称和值
		"""
		# 检查默认配置文件路径的有效性
		return PathChecker.check_path(file_path)

	@staticmethod
	def handle_exception(exception: Exception, error_type: str) -> None:
		"""
		统一异常处理逻辑

		参数:
		exception: Exception - 遇到的异常对象
		error_type: str - 异常的类型或分类

		返回值:
		None
		"""
		ErrorHandler.handle_exception(exception, error_type)  # 调用错误处理器的异常处理方法

	@staticmethod
	def get_config(environment: Optional[str] = None) -> Dict[str, Any]:
		"""
		根据环境加载相应的配置。如果未指定环境，则使用默认环境。
		:param environment: 指定的环境名称，如果不提供，则使用ENVIRONMENT环境变量的值。
		:return: 加载的配置字典。
		"""
		# 使用环境变量或者默认值
		environment = environment or EnvironmentUtil.get_environment()

		# 构建配置文件路径
		config_file = f"{os.path.dirname(__file__)}\\config\\{environment}.json"

		# 尝试加载配置文件
		try:
			config = ConfigLoader.load_config(config_file)
			logging.info(f"Successfully loaded config: {config_file}")
			return config
		except (FileNotFoundError, ValueError) as e:
			# 当配置文件不存在或格式错误时，提供默认配置
			logging.error(f"Failed to load config: {config_file}. Error: {e}")
			return ConfigManager._default_config()
		except Exception as e:
			# 其他未知异常，记录并返回默认配置
			logging.error(f"An unexpected error occurred while loading config: {config_file}. Error: {e}")
			return ConfigManager._default_config()

	@staticmethod
	def _default_config() -> Dict[str, Any]:
		"""
		返回一个空的或者预定义的默认配置。

		本静态方法旨在提供一个配置的默认值，这些默认值可以是空的，也可以是预先定义的一组配置项。
		这个方法不接受任何参数。

		返回值:
			Dict[str, Any]: 一个字典，包含预定义的配置键值对。若无预定义配置，则返回空字典。
		"""
		# 根据实际需求，此处可返回一个包含默认配置的字典，例如：
		# return {
		#    'param1': 'default_value1',
		#    'param2': 'default_value2',
		# }
		# 然而，根据规定，此处也完全可以只返回一个空字典，因为注释中已经说明了可能的情况
		return {}


if __name__ == '__main__':
	# 使用示例
	config_manager = ConfigManager()
	# config = config_manager.load_config(r"E:/work_files/projects/python_learning/配置文件/config/default.json")
	config = config_manager.get_config()
	print(config)
